package com.szalay.useradmin;

import java.io.File;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Dictionary;
import java.util.Enumeration;
import java.util.List;
import java.util.concurrent.Executor;
import java.util.concurrent.Executors;
import org.osgi.framework.Filter;
import org.osgi.framework.FrameworkUtil;
import org.osgi.framework.InvalidSyntaxException;
import org.osgi.service.useradmin.Authorization;
import org.osgi.service.useradmin.Role;
import org.osgi.service.useradmin.User;
import org.osgi.service.useradmin.UserAdmin;
import org.osgi.service.useradmin.UserAdminEvent;
import org.osgi.service.useradmin.UserAdminListener;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * UserAdmin implementation.
 */
public class UserAdminImpl implements UserAdmin {
    /** Logger. */
    private final Logger logger = LoggerFactory.getLogger(getClass());

    /** Data directory. */
    private final File dataDirectory;
    
    /** Dictionary with types of roles. */
    private final Dictionary types;
    
    /** Listeners. */
    private final List<UserAdminListener> listeners = new ArrayList<UserAdminListener>();
    
    /** Thread pool to inform listeners. */
    private final Executor executor = Executors.newFixedThreadPool(16);
    
    /**
     *  Create a new user admin impl.
     * @param dir data directory
     */
    public UserAdminImpl(final String dir) {
        logger.info("Starting com.szalay.useradmin...");
        dataDirectory = new File(dir);
        if (!dataDirectory.exists()) {
            dataDirectory.mkdirs();
        }
        types = new WriteBackDictionary(dataDirectory, "types");
        logger.info("com.szalay.useradmin started, storing data at '{}'", dataDirectory.getAbsolutePath());
    }
    
    /**
     * A new listener arrives.
     * @param l the listener.
     */
    public void bind(final UserAdminListener l) {
        if (l == null) {
            return;
        }
        synchronized (listeners) {
            listeners.add(l);
        }
    }
    
    /**
     * A listener disappears.
     * @param l the listener
     */
    public void unbind(final UserAdminListener l) {
        if (l == null) {
            return;
        }
        synchronized (listeners) {
            listeners.remove(l);
        }
    }
    
    private void fireListeners(final UserAdminEvent event) {
        synchronized (listeners) {
            for (final UserAdminListener l : listeners) {
                executor.execute(new Runnable() {
                    @Override
                    public void run() {
                        l.roleChanged(event);
                    }
                });
            }
        }
    }
    
    @Override
    public Role createRole(final String name, final int type) {
        if (name == null) {
            throw new IllegalArgumentException("Illegal name: null.");
        }
        if (name.equals(User.USER_ANYONE)) {
            throw new IllegalArgumentException("Illegal name: " + User.USER_ANYONE);
        }
        if ((type != Role.GROUP) && (type != Role.ROLE) && (type != Role.USER)) {
            throw new IllegalArgumentException("Illegal type: " + type);
        }
        if (checkIfStorageExists(name)) {
            logger.info("Role already exists: '{}'", name);
            return null;
        }
        
        Role result;
        if (type == Role.GROUP) {
            result = new GroupImpl(name, getStorageDirectoryForName(name), this);
            types.put(name, Integer.valueOf(Role.GROUP));
        }
        else if (type == Role.ROLE) {
            result = new RoleImpl(name, getStorageDirectoryForName(name));
            types.put(name, Integer.valueOf(Role.ROLE));
        }
        else {
            result = new UserImpl(name, getStorageDirectoryForName(name));
            types.put(name, Integer.valueOf(Role.USER));
        }
        
        //Fire event
        final UserAdminEvent event = new UserAdminEvent(null, UserAdminEvent.ROLE_CREATED, result);
        fireListeners(event);
        
        return result;
    }
    
    private File getStorageDirectoryForName(final String name) {
        try {
            final File storage = new File(dataDirectory.getAbsolutePath() + "/" + URLEncoder.encode(name, "utf-8"));
            if (!storage.exists()) {
                storage.mkdirs();
            }
            return storage;
        } 
        catch (UnsupportedEncodingException ex) {
            logger.error(null, ex);
            throw new IllegalStateException(ex);
        }
    }
    
    private boolean checkIfStorageExists(final String name) {
        try {
            final File storage = new File(dataDirectory.getAbsolutePath() + "/" + URLEncoder.encode(name, "utf-8"));
            return storage.exists();
        } 
        catch (UnsupportedEncodingException ex) {
            logger.error(null, ex);
            throw new IllegalStateException(ex);
        }
    }
    
    @Override
    public boolean removeRole(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Illegal name: null.");
        }
        if (!checkIfStorageExists(name)) {
            return false;
        }
        final Role toRemove = getRole(name);
        final File storage = getStorageDirectoryForName(name);
        deleteDirectory(storage);
        logger.info("Deleted role: '{}'", name);
        
        //Fire event
        final UserAdminEvent event = new UserAdminEvent(null, UserAdminEvent.ROLE_REMOVED, toRemove);
        fireListeners(event);
        
        return true;
    }
    
    private void deleteDirectory(final File root) {
        if (root.isDirectory()) {
            //Recursivly delete...
            File[] children = root.listFiles();
            for (final File child : children) {
                deleteDirectory(child);
            }
        }
        root.delete();
    }

    @Override
    public Role getRole(String name) {
        if (name == null) {
            throw new IllegalArgumentException("Illegal name: null.");
        }
        if (!checkIfStorageExists(name)) {
            return null;
        }
        final Integer type = Integer.valueOf((String) types.get(name));
        if (type.intValue() == Role.USER) {
            return new UserImpl(name, getStorageDirectoryForName(name));
        }
        else if (type.intValue() == Role.GROUP) {
            return new GroupImpl(name, getStorageDirectoryForName(name), this);
        }
        return new RoleImpl(name, getStorageDirectoryForName(name));
    }

    @Override
    public Role[] getRoles(final String filter) throws InvalidSyntaxException {
        //Returns all objects in this user manager
        final Enumeration<String> keys = types.keys();
        final List<Role> result = new ArrayList<Role>(types.size());
        Filter filterObj = null;
        if (filter != null) {
            filterObj = FrameworkUtil.createFilter(filter);
        }
        
        while (keys.hasMoreElements()) {
            final String objectName = keys.nextElement();
            final Role roleForName = getRole(objectName);
            
            //Check filter if there is one...
            if (filterObj != null) {
                if (!filterObj.match(roleForName.getProperties())) {
                    continue;
                }
            }
            result.add(roleForName);
        }
        return result.toArray(new Role[] { });
    }
    
    @Override
    public User getUser(final String property, final String value) {
        if (property == null) {
            throw new IllegalArgumentException("Illegal property: null.");
        }
        if (value == null) {
            throw new IllegalArgumentException("Illegal value: null.");
        }
        
        final Enumeration keys = types.keys();
        while (keys.hasMoreElements()) {
            final String roleName = (String) keys.nextElement();
            final Role role = getRole(roleName);
            if (role.getType() != Role.USER) {
                continue;
            }
            
            final User user = (User) role;
            final Object propertyValue = user.getProperties().get(property);
            if (propertyValue == null) {
                continue;
            }
            if (propertyValue.equals(value)) {
                return user;
            }
        }
        return null;
    }

    @Override
    public Authorization getAuthorization(User user) {
        if (user == null) {
            return new AuthorizationImpl(User.USER_ANYONE, this);
        }
        return new AuthorizationImpl(user.getName(), this);
    }
}
